package org.jenkinsci.plugins.qywechat;

import org.jenkinsci.plugins.qywechat.dto.BuildBeginInfo;
import org.jenkinsci.plugins.qywechat.dto.BuildMentionedInfo;
import org.jenkinsci.plugins.qywechat.dto.BuildOverInfo;
import org.jenkinsci.plugins.qywechat.model.NotificationConfig;
import com.arronlong.httpclientutil.exception.HttpProcessException;
import hudson.EnvVars;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.*;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Publisher;
import jenkins.tasks.SimpleBuildStep;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;

import java.io.IOException;
import java.io.PrintStream;

/**
 * 企业微信构建通知
 * 
 * @author jiaju
 */
public class QyWechatNotification extends Publisher implements SimpleBuildStep {
	// 当前构建job中配置的相关参数
	// 构建环境名称
	public String topicName = "";
	// Webhook地址
	private String webhookUrl;
	// 发送开始构建信息
	private boolean startBuild = false;
	// 仅失败才@
	private boolean failNotify;
	// 仅失败才发送
	private boolean failSend = false;
	// 仅成功才发送
	private boolean successSend = false;
	// 仅构建中断才发送
	private boolean abortSend = false;
	// 仅不稳定构建才发送
	private boolean unstableSend = false;
	// 通知UserID
	private String mentionedId;
	// 通知手机号码
	private String mentionedMobile;
	// 在构建日志中记录推送消息
	private boolean printPushMsgOnBuildLog = false;

	// 项目名称
	private String projectName;

	@Extension
	public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();

	@DataBoundConstructor
	public QyWechatNotification() {
	}

	/**
	 * 开始执行构建
	 * 
	 * @param build
	 * @param listener
	 * @return
	 */
	@Override
	public boolean prebuild(AbstractBuild<?, ?> build, BuildListener listener) {
		EnvVars envVars;
		try {
			envVars = build.getEnvironment(listener);
		} catch (Exception e) {
			listener.getLogger().println("读取环境变量异常" + e.getMessage());
			envVars = new EnvVars();
		}
		NotificationConfig config = getConfig(envVars);
		if (StringUtils.isEmpty(config.webhookUrl)) {
			return true;
		}
		this.projectName = build.getProject().getFullDisplayName();
		BuildBeginInfo buildInfo = new BuildBeginInfo(this.projectName, build, config);

		String req = buildInfo.toJSONString();
		if(printPushMsgOnBuildLog) {
			listener.getLogger().println("推送通知" + req);
		}

		// 执行推送
		push(listener.getLogger(), config.webhookUrl, req, config);
		return true;
	}

	/**
	 * 构建结束
	 * 
	 * @param run
	 * @param workspace
	 * @param launcher
	 * @param listener
	 * @throws InterruptedException
	 * @throws IOException
	 */
	@Override
	public void perform(Run<?, ?> run, FilePath workspace, Launcher launcher, TaskListener listener)
			throws InterruptedException, IOException {
		NotificationConfig config = getConfig(run.getEnvironment(listener));
		if (StringUtils.isEmpty(config.webhookUrl)) {
			return;
		}
		Result result = run.getResult();
		listener.getLogger().println("构建job执行结果[" + result + "]");
		
		// 运行不成功
		if (null == result) {
			listener.getLogger().println("未获取到构建job执行结果");
			return;
		}
		if ((result.equals(Result.SUCCESS) && config.successSend) 
				|| (result.equals(Result.ABORTED) && config.abortSend)
				|| (result.equals(Result.FAILURE) && config.failSend)
				|| (result.equals(Result.UNSTABLE) && config.unstableSend)) {
			// 设置当前项目名称
			if (run instanceof AbstractBuild) {
				this.projectName = run.getParent().getFullDisplayName();
			}

			// 构建结束通知
			BuildOverInfo buildInfo = new BuildOverInfo(this.projectName, run, config);

			String req = buildInfo.toJSONString();
			if(printPushMsgOnBuildLog) {
				listener.getLogger().println("推送通知" + req);
			}

			// 推送结束通知
			push(listener.getLogger(), config.webhookUrl, req, config);

			// 仅在失败的时候，才进行@
			if (!result.equals(Result.SUCCESS) || !config.failNotify) {
				// 没有填写UserId和手机号码
				if (StringUtils.isEmpty(config.mentionedId) && StringUtils.isEmpty(config.mentionedMobile)) {
					listener.getLogger().println("未配置要被@的用户信息，请检查...");
					return;
				}

				// 构建@通知
				BuildMentionedInfo consoleInfo = new BuildMentionedInfo(run, config);

				req = consoleInfo.toJSONString();
				if(printPushMsgOnBuildLog) {
					listener.getLogger().println("推送通知" + req);
				}
				// 执行推送
				push(listener.getLogger(), config.webhookUrl, req, config);
			}
		}
	}

	/**
	 * 推送消息
	 * 
	 * @param logger
	 * @param url
	 * @param data
	 * @param config
	 */
	private void push(PrintStream logger, String url, String data, NotificationConfig config) {
		String[] urls;
		if (url.contains(",")) {
			urls = url.split(",");
		} else {
			urls = new String[] { url };
		}
		for (String u : urls) {
			try {
				String msg = NotificationUtil.push(u, data, config);
				if(printPushMsgOnBuildLog) {
					logger.println("通知结果" + msg);
				}
			} catch (HttpProcessException e) {
				logger.println("通知异常" + e.getMessage());
				e.printStackTrace();
			}
		}
	}

	@Override
	public BuildStepMonitor getRequiredMonitorService() {
		return BuildStepMonitor.BUILD;
	}

	/**
	 * 读取配置，将当前Job的配置参数与全局配置参数整合
	 * 
	 * @param envVars
	 * @return
	 */
	public NotificationConfig getConfig(EnvVars envVars) {
		NotificationConfig config = DESCRIPTOR.getUnsaveConfig();
		if (StringUtils.isNotEmpty(webhookUrl)) {
			config.webhookUrl = webhookUrl;
		}
		if (StringUtils.isNotEmpty(mentionedId)) {
			config.mentionedId = mentionedId;
		}
		if (StringUtils.isNotEmpty(mentionedMobile)) {
			config.mentionedMobile = mentionedMobile;
		}
		config.topicName = topicName;
		config.failNotify = failNotify;
		config.abortSend = abortSend;
		config.unstableSend = unstableSend;
		config.failSend = failSend;
		config.successSend = successSend;
		config.startBuild = startBuild;
		config.printPushMsgOnBuildLog = printPushMsgOnBuildLog;
		// 使用环境变量
		if (config.webhookUrl.contains("$")) {
			String val = NotificationUtil.replaceMultipleEnvValue(config.webhookUrl, envVars);
			config.webhookUrl = val;
		}
		if (config.mentionedId.contains("$")) {
			String val = NotificationUtil.replaceMultipleEnvValue(config.mentionedId, envVars);
			config.mentionedId = val;
		}
		if (config.mentionedMobile.contains("$")) {
			String val = NotificationUtil.replaceMultipleEnvValue(config.mentionedMobile, envVars);
			config.mentionedMobile = val;
		}
		return config;
	}

	/** 下面为GetSet方法，当前Job保存时进行绑定 **/
	@DataBoundSetter
	public void setTopicName(String topicName) {
		this.topicName = topicName;
	}

	@DataBoundSetter
	public void setWebhookUrl(String webhookUrl) {
		this.webhookUrl = webhookUrl;
	}

	@DataBoundSetter
	public void setMentionedId(String mentionedId) {
		this.mentionedId = mentionedId;
	}

	@DataBoundSetter
	public void setMentionedMobile(String mentionedMobile) {
		this.mentionedMobile = mentionedMobile;
	}

	@DataBoundSetter
	public void setFailNotify(boolean failNotify) {
		this.failNotify = failNotify;
	}

	@DataBoundSetter
	public void setFailSend(boolean failSend) {
		this.failSend = failSend;
	}

	@DataBoundSetter
	public void setSuccessSend(boolean successSend) {
		this.successSend = successSend;
	}

	@DataBoundSetter
	public void setAbortSend(boolean abortSend) {
		this.abortSend = abortSend;
	}

	@DataBoundSetter
	public void setUnstableSend(boolean unstableSend) {
		this.unstableSend = unstableSend;
	}

	@DataBoundSetter
	public void setStartBuild(boolean startBuild) {
		this.startBuild = startBuild;
	}
	
	@DataBoundSetter
	public void setPrintPushMsgOnBuildLog(boolean printPushMsgOnBuildLog) {
		this.printPushMsgOnBuildLog = printPushMsgOnBuildLog;
	}

	public boolean isPrintPushMsgOnBuildLog() {
		return printPushMsgOnBuildLog;
	}
	
	public String getTopicName() {
		return topicName;
	}

	public String getWebhookUrl() {
		return this.webhookUrl;
	}

	public String getMentionedId() {
		return this.mentionedId;
	}

	public String getMentionedMobile() {
		return this.mentionedMobile;
	}

	public boolean isFailNotify() {
		return this.failNotify;
	}

	public boolean isStartBuild() {
		return this.startBuild;
	}

	public boolean isAbortSend() {
		return this.abortSend;
	}

	public boolean isUnstableSend() {
		return this.unstableSend;
	}

	public boolean isFailSend() {
		return this.failSend;
	}

	public boolean isSuccessSend() {
		return this.successSend;
	}
}
